﻿using System.Globalization;
using System.Reflection;
using System.Text.Json.Serialization;
using Newtonsoft.Json;

namespace Devonline.Core
{
    public static class CollectionExtensions
    {
        /// <summary>
        /// 集合是否为空或 null
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsNullOrEmpty<T>(this IEnumerable<T> source) => source == null || (!source.Any());
        /// <summary>
        /// 集合是否不为空或 null
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsNotNullOrEmpty<T>(this IEnumerable<T> source) => source != null && source.Any();

        /// <summary>
        /// 集合乱序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static IEnumerable<T> ToShuffle<T>(this IEnumerable<T> source)
        {
            var count = source.Count();
            var temps = source.ToList();
            var targets = new List<T>(count);
            var random = new Random();
            while (count != 0)
            {
                var index = random.Next(0, count);
                targets.Add(temps[index]);
                temps.RemoveAt(index);
                count--;
            }

            return targets;
        }

        /// <summary>
        /// Collection ToString extension method, concatenate all collection object to a format string 
        /// </summary>
        /// <typeparam name="T">collection object type</typeparam>
        /// <param name="collection">collection object instance</param>
        /// <param name="format">object format, default is "{0}"</param>
        /// <param name="spliter">object spliter, default is "; "</param>
        /// <returns>concatenate all collection object to a format string</returns>
        public static string ToString<T>(this IEnumerable<T> collection, string spliter = AppSettings.DEFAULT_SPLITER_STRING, string format = AppSettings.DEFAULT_FORMAT_STRING)
        {
            var result = new List<string>();
            foreach (var item in collection)
            {
                if (item != null)
                {
                    var value = string.Format(CultureInfo.CurrentCulture, format, item);
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        result.Add(value);
                    }
                }
            }

            return string.Join(spliter, result);
        }

        /// <summary>
        /// 从类似于 url 的字符串中截取参数列表到键值对
        /// </summary>
        /// <param name="value"></param>
        /// <param name="outer"></param>
        /// <param name="inner"></param>
        /// <returns></returns>
        public static ICollection<KeyValuePair<string, string>>? ToKeyValuePairs(this string value, char outer = AppSettings.CHAR_ADD, char inner = AppSettings.CHAR_EQUAL)
        {
            var pairs = value.Split(outer, StringSplitOptions.RemoveEmptyEntries);
            if (pairs != null && pairs.Length > 0)
            {
                var values = new Dictionary<string, string>();
                foreach (var pair in pairs)
                {
                    if (pair.IsNotNullOrEmpty())
                    {
                        var keyValue = pair.Split(inner, StringSplitOptions.RemoveEmptyEntries);
                        var key = keyValue[0];
                        if (key.IsNotNullOrEmpty())
                        {
                            values.Add(key, keyValue[1] ?? string.Empty);
                        }
                    }
                }

                return values;
            }

            return null;
        }
        /// <summary>
        /// 将键值对转换为类似于 url 的包含参数的字符串
        /// </summary>
        /// <param name="keyValuePairs"></param>
        /// <param name="useCamelCase">字段名使用驼峰法</param>
        /// <param name="outer"></param>
        /// <param name="inner"></param>
        /// <returns></returns>
        public static string ToString(this ICollection<KeyValuePair<string, object>> keyValuePairs, bool useCamelCase = true, char outer = AppSettings.CHAR_ADD, char inner = AppSettings.CHAR_EQUAL)
        {
            var strings = new List<string>();
            foreach (var keyValue in keyValuePairs)
            {
                if (keyValue.Value != null)
                {
                    strings.Add((useCamelCase ? keyValue.Key.FirstCharToLower() : keyValue.Key) + inner + keyValue.Value);
                }
            }

            return string.Join(outer, strings);
        }
        /// <summary>
        /// 将键值对转换为对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="keyValuePairs"></param>
        /// <param name="useCamelCase">字段名使用驼峰法</param>
        /// <returns></returns>
        public static T ToObject<T>(this ICollection<KeyValuePair<string, object>> keyValuePairs, bool useCamelCase = true) where T : class, new()
        {
            var t = new T();
            var type = typeof(T);
            foreach (var keyValue in keyValuePairs)
            {
                var propertyInfo = type.GetProperty(useCamelCase ? keyValue.Key.FirstCharToUpper() : keyValue.Key);
                if (propertyInfo != null)
                {
                    propertyInfo.SetValue(t, keyValue.Value == null ? null : Convert.ChangeType(keyValue.Value, propertyInfo.PropertyType, CultureInfo.CurrentCulture));
                }
            }

            return t;
        }
        /// <summary>
        /// 将对象公共成员属性转为键值对形式
        /// </summary>
        /// <typeparam name="T">转换对象类型</typeparam>
        /// <param name="t">转换对象</param>
        /// <param name="useCamelCase">字段名使用驼峰法</param>
        /// <param name="useJsonProperty">使用 JsonProperty 特性指定的字段名</param>
        /// <returns></returns>
        public static ICollection<KeyValuePair<string, object>> ToKeyValuePairs<T>(this T t, bool useCamelCase = true, bool useJsonProperty = true) where T : class
        {
            var keyValuePairs = new List<KeyValuePair<string, object>>();
            var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            if (propertyInfos.Length > 0)
            {
                foreach (var propertyInfo in propertyInfos)
                {
                    var value = propertyInfo.GetValue(t);
                    if (value != null)
                    {
                        var key = string.Empty;
                        if (useJsonProperty)
                        {
                            key = propertyInfo.GetAttributeValue<JsonPropertyAttribute, string>(nameof(JsonPropertyAttribute.PropertyName)) ?? propertyInfo.GetAttributeValue<JsonPropertyNameAttribute, string>(nameof(JsonPropertyAttribute.PropertyName));
                        }

                        key ??= (useCamelCase ? propertyInfo.Name.FirstCharToLower() : propertyInfo.Name);
                        keyValuePairs.Add(new KeyValuePair<string, object>(key, value));
                    }
                }
            }

            return keyValuePairs;
        }

        /// <summary>
        /// 字节流转字符串数组
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="spliter"></param>
        /// <returns></returns>
        public static string ToHexString(this IEnumerable<byte> bytes, char spliter = AppSettings.CHAR_SPACE)
        {
            var value = BitConverter.ToString(bytes.ToArray());
            if (spliter != AppSettings.CHAR_HLINE)
            {
                value = value.Replace(AppSettings.CHAR_HLINE, spliter);
            }

            return value;
        }
        /// <summary>
        /// 字符串数组转字节流
        /// </summary>
        /// <param name="hexString"></param>
        /// <param name="spliter"></param>
        /// <returns></returns>
        public static byte[]? ToBinary(this string hexString, char spliter = AppSettings.CHAR_SPACE)
        {
            if (spliter == char.MinValue)
            {
                var bytes = new byte[hexString.Length / AppSettings.UNIT_TWO];
                for (int i = 0; i < hexString.Length; i += AppSettings.UNIT_TWO)
                {
                    bytes[i] = Convert.ToByte(hexString[i..AppSettings.UNIT_TWO], AppSettings.UNIT_SIXTEEN);
                }

                return bytes;
            }
            else
            {
                var binaryArray = hexString.Split(spliter);
                if (binaryArray.Length > 0)
                {
                    var bytes = new byte[binaryArray.Length];
                    for (int i = 0; i < binaryArray.Length; i++)
                    {
                        if (binaryArray[i].IsNotNullOrEmpty())
                        {
                            bytes[i] = Convert.ToByte(binaryArray[i], AppSettings.UNIT_SIXTEEN);
                        }
                    }

                    return bytes;
                }
            }

            return null;
        }

        /// <summary>
        /// 添加或更新键值对
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="this"></param>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <returns></returns>
        public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> @this, TKey key, TValue value)
        {
            if (@this.ContainsKey(key))
            {
                @this[key] = value;
            }
            else
            {
                @this.Add(new KeyValuePair<TKey, TValue>(key, value));
            }

            return @this[key];
        }
        /// <summary>
        /// 添加或更新键值对
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="this"></param>
        /// <param name="key">键</param>
        /// <param name="addValue">添加时的值</param>
        /// <param name="updateValueHanlder">更新时的操作</param>
        /// <returns></returns>
        public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> @this, TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueHanlder)
        {
            if (@this.ContainsKey(key))
            {
                @this[key] = updateValueHanlder(key, @this[key]);
            }
            else
            {
                @this.Add(new KeyValuePair<TKey, TValue>(key, addValue));
            }

            return @this[key];
        }
        /// <summary>
        /// 添加或更新键值对
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="this"></param>
        /// <param name="key">键</param>
        /// <param name="addValueHanlder">添加时的操作</param>
        /// <param name="updateValueHanlder">更新时的操作</param>
        /// <returns></returns>
        public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> @this, TKey key, Func<TKey, TValue> addValueHanlder, Func<TKey, TValue, TValue> updateValueHanlder)
        {
            if (@this.ContainsKey(key))
            {
                @this[key] = updateValueHanlder(key, @this[key]);
            }
            else
            {
                @this.Add(new KeyValuePair<TKey, TValue>(key, addValueHanlder(key)));
            }

            return @this[key];
        }
        /// <summary>
        /// 移除符合条件的元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="this"></param>
        /// <param name="where"></param>
        public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where) => @this.Where(where).ToList().ForEach(x => @this.Remove(x));

        /// <summary>
        /// 添加多个元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="this"></param>
        /// <param name="values"></param>
        public static void AddRange<T>(this ICollection<T> @this, IEnumerable<T> values)
        {
            foreach (var value in values)
            {
                @this.Add(value);
            }
        }
        /// <summary>
        /// 添加符合条件的多个元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="this"></param>
        /// <param name="predicate"></param>
        /// <param name="values"></param>
        public static void AddRange<T>(this ICollection<T> @this, Func<T, bool> predicate, params T[] values)
        {
            foreach (var value in values)
            {
                if (predicate(value))
                {
                    @this.Add(value);
                }
            }
        }
        /// <summary>
        /// 添加不重复的元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="this"></param>
        /// <param name="values"></param>
        public static void AddRangeIfNotContains<T>(this ICollection<T> @this, params T[] values)
        {
            foreach (T value in values)
            {
                if (!@this.Contains(value))
                {
                    @this.Add(value);
                }
            }
        }

        /// <summary>
        /// 原始对象集合转为目标对象集合
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TTarget"></typeparam>
        /// <param name="sources"></param>
        /// <param name="convert"></param>
        /// <returns></returns>
        public static IEnumerable<TTarget>? ConvertAll<TSource, TTarget>(this IEnumerable<TSource> sources, Func<TSource, TTarget>? convert = null)
        {
            var sourceType = sources.GetType().GetElementType();
            var targetType = typeof(TTarget);

            //输入模型类型和输出模型类型不匹配时, 进行数据转换, 依据当前流程节点配置的 schame 进行转换
            if (sources == null || (!sources.Any()) || sources is IEnumerable<TTarget>)
            {
                return sources as IEnumerable<TTarget>;
            }

            // 整体转换不可用, 因为转换不了内部会抛异常, 导致整个数据转换失败
            // Data = Data.ToList().ConvertAll(ConvertModel);
            var targets = new List<TTarget>();
            Parallel.ForEach(sources, source =>
            {
                if (source != null)
                {
                    TTarget target = (convert != null) ? convert(source) : source.CopyTo<TTarget>();
                    if (target != null)
                    {
                        lock (targets)
                        {
                            targets.Add(target);
                        }
                    }
                }
            });

            return targets;
        }

        /// <summary>
        /// 返回一个集合的新的引用, 并据参数清空旧的集合
        /// TODO 有线程问题
        /// </summary>
        /// <typeparam name="T">原始集合泛型类型</typeparam>
        /// <param name="data">原始集合</param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static ICollection<T>? GetFromCollection<T>(this ICollection<T> data, int count = 0)
        {
            ICollection<T>? list = null;
            if (data.Count > 0)
            {
                lock (data)
                {
                    if (data.Count > 0)
                    {
                        var all = false;
                        if (count <= 0 || count >= data.Count)
                        {
                            count = data.Count;
                            all = true;
                        }

                        list = all ? data.ToArray() : data.Take(count).ToArray();
                        if (all)
                        {
                            data.Clear();
                        }
                        else
                        {
                            foreach (var t in list)
                            {
                                data.Remove(t);
                            }
                        }
                    }
                }
            }

            return list;
        }
        /// <summary>
        /// 返回队列的 count 个元素副本，并根据参数决定是否要清空集合
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queue"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static ICollection<T>? GetFromQueue<T>(this Queue<T> queue, int count = 0)
        {
            ICollection<T>? list = null;
            if (queue.Count > 0)
            {
                lock (queue)
                {
                    if (queue.Count > 0)
                    {
                        var all = false;
                        if (count <= 0 || count >= queue.Count)
                        {
                            count = queue.Count;
                            all = true;
                        }

                        if (all)
                        {
                            list = queue.ToArray();
                            queue.Clear();
                        }
                        else
                        {
                            list = new T[count];
                            for (int i = 0; i < count; i++)
                            {
                                list.Add(queue.Dequeue());
                            }
                        }
                    }
                }
            }

            return list;
        }
        /// <summary>
        /// 将集合中所有元素放入新队列中并返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Queue<T> ToQueue<T>(this ICollection<T> data)
        {
            var queue = new Queue<T>();
            if (data.Count != 0)
            {
                Parallel.ForEach(data, t =>
                {
                    lock (queue)
                    {
                        queue.Enqueue(t);
                    }
                });
            }

            return queue;
        }
        /// <summary>
        /// 将集合中的数据并行放入队列
        /// </summary>
        /// <returns></returns>
        public static void MoveToQueue<T>(this ICollection<T> data, Queue<T> queue)
        {
            if (data.Count > 0)
            {
                var list = data.IsReadOnly ? data : data.GetFromCollection();
                if (list != null && list.Count > 0)
                {
                    Parallel.ForEach(list, t =>
                    {
                        lock (queue)
                        {
                            queue.Enqueue(t);
                        }
                    });
                }
            }
        }
        /// <summary>
        /// 从队列中弹出并返回一个值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queue"></param>
        /// <returns></returns>
        public static T? GetFromQueue<T>(this Queue<T> queue)
        {
            T? t = default;
            if (queue.Count > 0)
            {
                lock (queue)
                {
                    if (queue.Count != 0)
                    {
                        t = queue.Dequeue();
                    }
                }
            }

            return t;
        }

        /// <summary>
        /// 对处理当前数据处理集合中的所有数据并行或顺序执行 execute 方法
        /// </summary>
        /// <typeparam name="T">数据类型参数</typeparam>
        /// <param name="data">处理集合</param>
        /// <param name="execute">处理方法</param>
        /// <param name="isParallel">是否并发执行, true 则并发执行, false 则顺序执行, 默认 false</param>
        /// <returns></returns>
        public static void Execute<T>(this IEnumerable<T> data, Action<T> execute, bool isParallel = false)
        {
            if (data != null && data.Any())
            {
                if (isParallel)
                {
                    Parallel.ForEach(data, execute);
                }
                else
                {
                    foreach (var t in data)
                    {
                        execute(t);
                    }
                }
            }
        }
        /// <summary>
        /// 对处理当前数据处理集合中的所有数据并行或顺序执行 execute 方法到终结点
        /// </summary>
        /// <typeparam name="T">数据类型参数</typeparam>
        /// <param name="data">处理集合</param>
        /// <param name="execute">处理方法</param>
        /// <param name="isParallel">是否并发执行, true 则并发执行, false 则顺序执行, 默认 false</param>
        /// <returns></returns>
        public static void ExecuteToEndpoint<T>(this IEnumerable<T> data, IEndpoint endpoint, Action<IEndpoint, T> execute, bool isParallel = false)
        {
            if (data != null && data.Any())
            {
                if (isParallel)
                {
                    Parallel.ForEach(data, t => execute(endpoint, t));
                }
                else
                {
                    foreach (var t in data)
                    {
                        execute(endpoint, t);
                    }
                }
            }
        }
        /// <summary>
        /// 对处理当前数据处理集合中的所有数据并行或顺序执行 execute 方法
        /// </summary>
        /// <typeparam name="T">数据类型参数</typeparam>
        /// <param name="data">处理集合</param>
        /// <param name="execute">处理方法</param>
        /// <param name="isParallel">是否并发执行, true 则并发执行, false 则顺序执行, 默认 false</param>
        /// <returns></returns>
        public static async Task ExecuteAsync<T>(this IEnumerable<T> data, Func<T, Task> execute, bool isParallel = false)
        {
            if (data != null && data.Any())
            {
                if (isParallel)
                {
                    //由于 Parallel.ForEach 不支持异步并行, 因此使用任务组
                    var tasks = new List<Task>();
                    foreach (var t in data)
                    {
                        tasks.Add(Task.Run(async () => await execute(t).ConfigureAwait(false)));
                    }

                    Task.WaitAll(tasks.ToArray());
                }
                else
                {
                    foreach (var t in data)
                    {
                        await execute(t).ConfigureAwait(false);
                    }
                }
            }
        }
        /// <summary>
        /// 对处理当前数据处理集合中的所有数据并行或顺序执行 execute 方法到终结点
        /// </summary>
        /// <typeparam name="T">数据类型参数</typeparam>
        /// <param name="data">处理集合</param>
        /// <param name="execute">处理方法</param>
        /// <param name="isParallel">是否并发执行, true 则并发执行, false 则顺序执行, 默认 false</param>
        /// <returns></returns>
        public static async Task ExecuteToEndpointAsync<T>(this IEnumerable<T> data, IEndpoint endpoint, Func<IEndpoint, T, Task> execute, bool isParallel = false)
        {
            if (data != null && data.Any())
            {
                if (isParallel)
                {
                    //由于 Parallel.ForEach 不支持异步并行, 因此使用任务组
                    var tasks = new List<Task>();
                    foreach (var t in data)
                    {
                        tasks.Add(Task.Run(async () => await execute(endpoint, t).ConfigureAwait(false)));
                    }

                    Task.WaitAll(tasks.ToArray());
                }
                else
                {
                    foreach (var t in data)
                    {
                        await execute(endpoint, t).ConfigureAwait(false);
                    }
                }
            }
        }
    }
}
